/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Assembler;

import Common.ObjectFileRecord;
import Common.Errors;
import Common.Instruction;
import Common.Utilities;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 *
 * @author Kyle
 */
public class AssemblerPassTwo
{
    List<IntermediateData> Data;
    Table SymbolTable;
    ObjectFile ObjFile;
    final String ProgramName;
    final int StartAddress;

    public AssemblerPassTwo(List<IntermediateData> data, Table symTable)
    {
        Data = data;
        SymbolTable = symTable;
        ObjFile = new ObjectFile();
        ProgramName = data.get(0).Label;
        StartAddress = Integer.valueOf(Data.get(0).Operands[0]);
    }

    public void Compute()
    {
        System.out.println("Beginning of Assembler Report");
        System.out.printf("%-4s %-4s %-3s %-4s %s", "LC", "Hex", "ARM", "Line", "Source Line").println();

        ObjectFileRecord header = new ObjectFileRecord();
        header.Data.add(ProgramName);
        header.Data.add(String.format("%04X", StartAddress));
        header.Data.add(String.format("%04X", (Data.get(Data.size()-1).LC - StartAddress)));
        header.Data.add(String.format("%04X", StartAddress));
        header.Data.add(new SimpleDateFormat("yyyyDD,HH,mm,ss").format(new Date()));
        header.Data.add(String.format("%04d", AssemblerMain.VERSION));
        ObjFile.AddRecord("H", header);

        for (IntermediateData d : Data)
        {
            Calculate(d);
        }
        ObjectFileRecord end = new ObjectFileRecord();
        end.Data.add(ProgramName);
        ObjFile.AddRecord("E", end);

        header.Data.add(String.format("%04X", ObjFile.NumRecords));
        header.Data.add(String.format("%04X", ObjFile.NumLRecords));
        header.Data.add(String.format("%04X", ObjFile.NumTRecords));
        header.Data.add(String.format("%04X", ObjFile.NumMRecords));
        header.Data.add("FFA-ASM");
        header.Data.add(ProgramName);

        System.out.println();
        

    }

    private void Calculate(IntermediateData data)
    {
        data.ComputeAbsolute();
        if (data.Type == Instruction.Type.OPERATOR)
        {
            String operand = "0";
            if (data.Operands.length > 1)
                operand = data.Operands[1];
            SymbolRow opSymbol = null;
            if (SymbolTable.Rows.containsKey(operand))
            {
                opSymbol = SymbolTable.Rows.get(operand);
                if (opSymbol.Data.EquValue != null)
                {
                    operand = opSymbol.Data.EquValue;
                }
                else if (opSymbol.Data.Directive != Instruction.GetDirective("EXTRN"))
                {
                    operand = String.valueOf(opSymbol.Data.LC);
                }
            }
            if (opSymbol != null)
            {
                if (opSymbol.Data.Directive == Instruction.GetDirective("EXTRN"))
                {
                    data.ModifyCount = 1;
                    data.Absolute = "M";
                    operand = "0";
                }
            }

            String code = "00000";
            if (!data.Errors.contains("InvalidFunction"))
            {
                code = Utilities.IntToBinary(data.Operator.Code, 2) + Utilities.IntToBinary(data.Function.Code, 3);
            }
            if (data.Operator == Instruction.GetOperator("CNTL"))
            {
                code += "0";
                code += Utilities.IntToBinary(EvaluateRlabel(operand, data), 10);

            }
            else if (data.Operator == Instruction.GetOperator("STACK"))
            {
                if (Utilities.ValidateLiteral(operand))
                {
                    code += "1";
                    code += Utilities.IntToBinary(Utilities.EvaluateLiteral(operand), 10);
                }
                else
                {
                    code += "0";
                    code += Utilities.IntToBinary(EvaluateRlabel(operand, data), 10);
                }
            }
            else if (data.Operator == Instruction.GetOperator("JUMP"))
            {
                code += "0";
                code += Utilities.IntToBinary(EvaluateRlabel(operand, data), 10);
            }
            else if (data.Operator == Instruction.GetOperator("SOPER"))
            {
                if (data.Function == data.Operator.Functions.get("READC") || data.Function == data.Operator.Functions.get("WRITEC"))
                {
                    code += "100";
                }
                else
                {
                    code += "000";
                }
                try
                {
                    code += Utilities.IntToBinary(Integer.valueOf(operand), 8);
                }
                catch (NumberFormatException ex)
                {
                    
                }
            }
            else if (data.Operator == Instruction.GetOperator("MOPER"))
            {
                if (data.Function == data.Operator.Functions.get("READC") || data.Function == data.Operator.Functions.get("WRITEC"))
                {
                    code += "1";
                }
                else
                {
                    code += "0";
                }
                try
                {
                    String temp = Utilities.IntToBinary(EvaluateRlabel(operand, data), 10);
                    if (temp.length() > 10)
                    {
                        code += Utilities.IntToBinary(0, 10);
                        data.Errors.add("InvalidSField");
                    }
                    else
                    {
                        code += temp;
                    }
                }
                catch (NumberFormatException ex)
                {

                }
            }


            int hexCode = Integer.valueOf(code, 2);

            System.out.printf("%4X %04X %s %4d %s\n", data.LC, hexCode, data.Absolute, data.LineNumber, data.Source_Line);

            boolean replace = false;
            for (String error : data.Errors)
            {
                replace &= Errors.ReplaceWithNOP(error);
            }
            if (replace)
            {
                AddNOP(data);
            }
            else
            {
                ObjectFileRecord textRecord = new ObjectFileRecord();
                textRecord.Data.add(String.format("%04X", data.LC));
                textRecord.Data.add(String.format("%04X", hexCode));
                textRecord.Data.add(data.Absolute);
                textRecord.Data.add(String.valueOf(data.ModifyCount));
                textRecord.Data.add(ProgramName);
                ObjFile.AddRecord("T", textRecord);

                // Uses external variable
                if (data.ModifyCount > 0)
                {
                    ObjectFileRecord modifyRecord = new ObjectFileRecord();
                    modifyRecord.Data.add(String.format("%04X", data.LC));
                    modifyRecord.Data.add(String.format("%04X", hexCode));
                    modifyRecord.Data.add("1");
                    modifyRecord.Data.add("+");
                    modifyRecord.Data.add(opSymbol.Data.Operands[0]);
                    modifyRecord.Data.add(ProgramName);
                    ObjFile.AddRecord("M", modifyRecord);
                }
            }
            
        }
        else if (data.Type == Instruction.Type.DIRECTIVE)
        {
            if (data.Directive == Instruction.GetDirective("START") ||
                data.Directive == Instruction.GetDirective("EQU") ||
                data.Directive == Instruction.GetDirective("EQUE") ||
                data.Directive == Instruction.GetDirective("END"))
            {
                System.out.printf("%4X %s %s %4d %s\n", data.LC, "    ", " ", data.LineNumber, data.Source_Line);
            }
            else if (data.Directive == Instruction.GetDirective("ENTRY"))
            {
                if (!SymbolTable.Rows.containsKey(data.Operands[0]))
                {
                    data.Errors.add("InvalidUndefinedLabel");
                }
                else
                {
                    ObjectFileRecord linkingRecord = new ObjectFileRecord();
                    linkingRecord.Data.add(data.Operands[0]);
                    linkingRecord.Data.add(String.format("%04X", SymbolTable.Rows.get(data.Operands[0]).Data.LC));
                    linkingRecord.Data.add(ProgramName);
                    ObjFile.AddRecord("L", linkingRecord);
                }
                System.out.printf("%4X %s %s %4d %s\n", data.LC, "    ", " ", data.LineNumber, data.Source_Line);

                
                
            }
            else if (data.Directive == Instruction.GetDirective("EXTRN"))
            {
                System.out.printf("%4X %s %s %4d %s\n", data.LC, "    ", " ", data.LineNumber, data.Source_Line);
                /*ObjectFileRecord modifyRecord = new ObjectFileRecord();
                modifyRecord.Data.add(String.format("%04X", data.LC));
                modifyRecord.Data.add("0000");
                modifyRecord.Data.add("1");
                modifyRecord.Data.add("+");
                modifyRecord.Data.add(data.Operands[0]);
                modifyRecord.Data.add(ProgramName);
                ObjFile.AddRecord("M", modifyRecord);*/
            }
            else if (data.Directive == Instruction.GetDirective("DAT"))
            {
                int value = 0;
                try
                {
                    value = Utilities.EvaluateLiteral(data.Operands[0]);
                }
                catch (NumberFormatException ex)
                {
                    data.Errors.add("InvalidDatVal");
                }
                System.out.printf("%4X %04X %s %4d %s\n", data.LC, value, data.Absolute, data.LineNumber, data.Source_Line);

                boolean replace = false;
                for (String error : data.Errors)
                {
                    if (error.equals("InvalidDatVal"))
                        replace = true;
                }
                if (replace)
                {
                    AddDat0(data);
                }
                else
                {
                    ObjectFileRecord textRecord = new ObjectFileRecord();
                    textRecord.Data.add(String.format("%04X", data.LC));
                    textRecord.Data.add(Utilities.IntTo4Hex(value));
                    textRecord.Data.add(data.Absolute);
                    textRecord.Data.add(String.valueOf(data.ModifyCount));
                    textRecord.Data.add(ProgramName);
                    ObjFile.AddRecord("T", textRecord);
                }
            }
            else if (data.Directive == Instruction.GetDirective("ADC")
                    || data.Directive == Instruction.GetDirective("ADCE"))
            {
                

                String[] list = data.Operands[0].split("(?=[\\+\\-])");

                if (data.Directive == Instruction.GetDirective("ADC"))
                {
                    if (list.length > 2)
                    {
                        data.Errors.add("InvalidADCCount");
                    }
                }

                int value = EvaluateADC(list, data);
                if (!(value >= -32768 && value <= 32767))
                {
                    data.Errors.add("InvalidDatVal");
                }
                if (data.Directive == Instruction.GetDirective("ADC"))
                {
                    if (value < StartAddress || value > (Data.get(Data.size()-1).LC))
                    {
                        data.Errors.add("InvalidADCAddress");
                    }
                }

                if (data.ModifyCount > 1)
                {
                    data.Absolute = "M";
                }
                System.out.printf("%4X %s %s %4d %s\n", data.LC, Utilities.IntTo4Hex(value), data.Absolute, data.LineNumber, data.Source_Line);

                                boolean replace = false;
                for (String error : data.Errors)
                {
                    if (error.equals("InvalidDatVal"))
                        replace = true;
                }
                if (replace)
                {
                    AddDat0(data);
                }
                else
                {
                    ObjectFileRecord textRecord = new ObjectFileRecord();
                    textRecord.Data.add(String.format("%04X", data.LC));
                    textRecord.Data.add(Utilities.IntTo4Hex(value));
                    textRecord.Data.add(data.Absolute);
                    if (data.Absolute.equals("M"))
                        textRecord.Data.add(String.valueOf(data.ModifyCount));
                    else
                        textRecord.Data.add(String.valueOf(0));
                    textRecord.Data.add(ProgramName);
                    ObjFile.AddRecord("T", textRecord);

                    if (data.Absolute.equals("M"))
                    {
                        CreateModifyRecords(list, data, value);
                    }
                }
            }
            else if (data.Directive == Instruction.GetDirective("RESET"))
            {
                PrintReportLineNoHex(data.LC, data.LineNumber, data.Source_Line);
                ObjectFileRecord linkingRecord = new ObjectFileRecord();
                linkingRecord.Data.add(data.Label);
                linkingRecord.Data.add(String.format("%04X", data.LC));
                linkingRecord.Data.add(ProgramName);
                ObjFile.AddRecord("L", linkingRecord);
            }
        }
        else if (data.Directive == Instruction.GetDirective("NOP"))
        {
            System.out.printf("%4X %s %s %4d %s\n", data.LC, "C000", data.Absolute, data.LineNumber, data.Source_Line);
            
            AddNOP(data);
        }
        else
        {
            PrintReportLineNoHex(data.LC, data.LineNumber, data.Source_Line);
            boolean replace = false;
            for (String error : data.Errors)
            {
                replace |= Errors.ReplaceWithNOP(error);
            }
            if (replace)
            {
                AddNOP(data);
            }
        }

        for (String error : data.Errors)
        {
            System.out.println("Error: " + Errors.GetErrorMessage(error));
        }
        
    }

    private void AddNOP(IntermediateData d)
    {
        ObjectFileRecord textRecord = new ObjectFileRecord();
        textRecord.Data.add(String.format("%04X", d.LC));
        textRecord.Data.add("C000");
        textRecord.Data.add("A");
        textRecord.Data.add("0");
        textRecord.Data.add(ProgramName);
        ObjFile.AddRecord("T", textRecord);
    }

    private void AddDat0(IntermediateData d)
    {
        ObjectFileRecord textRecord = new ObjectFileRecord();
        textRecord.Data.add(String.format("%04X", d.LC));
        textRecord.Data.add("0000");
        textRecord.Data.add("A");
        textRecord.Data.add("0");
        textRecord.Data.add(ProgramName);
        ObjFile.AddRecord("T", textRecord);
    }

//    private void PrintReportLine(int lc, int hex, String absolute, int lineNum, String source)
//    {
//        System.out.printf("%4X %04X %s %4d %s\n", lc, hex, absolute, lineNum, source);
//    }

    private void PrintReportLineNoHex(int lc, int lineNum, String source)
    {
        System.out.printf("%4X %s %s %4d %s\n", lc, "    ", " ", lineNum, source);
    }

    private int EvaluateADC(String[] list, IntermediateData d)
    {
        int result = 0;
        for (String s : list)
        {
            if (s.startsWith("-"))
            {
                result -= EvaluateADCOperand(s.substring(1), d);
            }
            else if (s.startsWith("+"))
            {
                result += EvaluateADCOperand(s.substring(1), d);
            }
            else
            {
                result += EvaluateADCOperand(s, d);
            }
        }

        return result;
    }

    private void CreateModifyRecords(String[] list, IntermediateData d, int value)
    {
        ObjectFileRecord modifyRecord = new ObjectFileRecord();
        modifyRecord.Data.add(String.format("%04X", d.LC));
        modifyRecord.Data.add(Utilities.IntTo4Hex(value));
        modifyRecord.Data.add(String.valueOf(d.ModifyCount));

        for (String s : list)
        {
            String type = "+";
            if (s.startsWith("+") || s.startsWith("-"))
            {
                type = s.substring(0, 1);
                s = s.substring(1);
            }
            if (SymbolTable.Rows.containsKey(s))
            {
                String base = GetBaseLabel(s);
                if (SymbolTable.Rows.containsKey(base))
                {
                    modifyRecord.Data.add(type);
                    modifyRecord.Data.add(base);
                }

            }
        }

        modifyRecord.Data.add(ProgramName);
        ObjFile.AddRecord("M", modifyRecord);
    }

    private int EvaluateADCOperand(String label, IntermediateData d)
    {
        if (label.equals("*"))
        {
            return d.LC;
        }
        else if(label.matches("\\d+"))
        {
            return Utilities.ParseInteger(label);
        }
        else if (SymbolTable.Rows.containsKey(label))
        {
            String base = GetBaseLabel(label);
            if (base.equals(label))
            {
                if (SymbolTable.Rows.containsKey(base))
                {
                    d.ModifyCount++;
                    if (SymbolTable.Rows.get(base).Usage.equals("EXTRN"))
                    {
                        d.Absolute = "M";
                    }
                    return SymbolTable.Rows.get(base).Data.LC;
                }
                else
                {
                    d.Errors.add("InvalidUndefinedLabel");
                    return 0;
                }

            }
            else
            {
                return EvaluateADCOperand(base, d);
            }
        }
        else
        {
            d.Errors.add("InvalidUndefinedLabel");
            return 0;
        }

    }

    private String GetBaseLabel(String label)
    {
        if (SymbolTable.Rows.containsKey(label))
        {
            if (SymbolTable.Rows.get(label).Data.EquValue != null)
            {
                return GetBaseLabel(SymbolTable.Rows.get(label).Data.EquValue);
            }
        }
        return label;
    }
    private int EvaluateRlabel(String label, IntermediateData d)
    {
        if (SymbolTable.Rows.containsKey(label))
        {
            if (SymbolTable.Rows.get(label).Data.Directive == Instruction.GetDirective("EXTRN"))
            {
                return 0;
            }
            else
            {
                return SymbolTable.Rows.get(label).Data.LC;
            }
        }
        else if (label.equals("*"))
        {
            return d.LC;
        }
        else if(label.matches("\\*[\\+\\-]\\d+"))
        {
            return d.LC + Utilities.ParseInteger(label.substring(1));
        }
        else if(label.matches("\\*[\\+\\-].+"))
        {
            if (SymbolTable.Rows.containsKey(label.substring(2, label.length())))
            {
                if (SymbolTable.Rows.get(label.substring(2, label.length())).Data.EquValue != null)
                {
                    if (SymbolTable.Rows.get(label.substring(2, label.length())).Data.EquValue.matches("\\d+"))
                    {
                        return d.LC + Utilities.ParseInteger(label.charAt(1) + SymbolTable.Rows.get(label.substring(2, label.length())).Data.EquValue);
                    }
                }
            }
        }
        try
        {
            return Integer.valueOf(label);
        }
        catch (NumberFormatException ex)
        {
            return 0;
        }
        
    }
}
